#include "znx_http_site_conf_mgr.h"
#include "comm/znx_crc.h"
#include <dirent.h>


static znx_http_site_mgr_t      *g_http_site_mgr = NULL;


typedef int (*znx_hash_node_cmp_pt)(znx_hash_node_t *node_a, znx_hash_node_t *node_b);
typedef void(*znx_hash_node_free_handle)(znx_hash_node_t *node);


static inline int
znx_http_site_hash_node_cmp(znx_hash_node_t *node_a, znx_hash_node_t *node_b)
{
    znx_http_site_conf_t    *http_site_conf_a, *http_site_conf_b;

    if (node_a->hash_key > node_b->hash_key) {
        return 1;
    }

    if (node_a->hash_key < node_b->hash_key) {
        return -1;
    }

    http_site_conf_a = znx_queue_data(node_a, znx_http_site_conf_t, hash_node);
    http_site_conf_b = znx_queue_data(node_b, znx_http_site_conf_t, hash_node);

    if (http_site_conf_a->server_name.len > http_site_conf_b->server_name.len) {
        return 1;
    }

    if (http_site_conf_a->server_name.len < http_site_conf_b->server_name.len) {
        return -1;
    }

    return znx_strncmp(http_site_conf_a->server_name.data,
        http_site_conf_b->server_name.data,
        http_site_conf_a->server_name.len);
}


static inline void
znx_http_site_hash_node_free(znx_hash_node_t *node)
{
    znx_http_site_conf_t    *http_site_conf;
    http_site_conf = znx_queue_data(node, znx_http_site_conf_t, hash_node);
    znx_http_site_conf_free(http_site_conf);
}


void
znx_g_http_site_mgr_create()
{
    g_http_site_mgr = znx_thread_calloc(sizeof(znx_http_site_mgr_t));
    g_http_site_mgr->hash_tab = znx_hash_tab_create(
        32, 128, znx_http_site_hash_node_cmp, znx_http_site_hash_node_free);
    uint64_t hash_bucket_num = znx_hash_tab_get_bucket_num(g_http_site_mgr->hash_tab);
    g_http_site_mgr->rwlocks = znx_thread_calloc(sizeof(znx_rwlock_t) * hash_bucket_num);
    for (uint64_t i = 0; i < hash_bucket_num; i++) {
        znx_rwlock_init(g_http_site_mgr->rwlocks + i);
    }
}


void znx_g_http_site_mgr_destroy()
{
    if (g_http_site_mgr == NULL) {
        return;
    }

    if (g_http_site_mgr->err.data) {
        znx_thread_free(g_http_site_mgr->err.data);
    }

    znx_hash_tab_destroy(g_http_site_mgr->hash_tab);
    znx_thread_free(g_http_site_mgr->rwlocks);
    znx_thread_free(g_http_site_mgr);
    g_http_site_mgr = NULL;
}


znx_bool_t
znx_g_http_site_mgr_init(const char *site_conf_dir)
{
    char        *errmsg = NULL;
    u_char      *buf = NULL;
    size_t      buf_len = 0;
    size_t      site_conf_dir_len = strlen(site_conf_dir);

    DIR *dir = opendir(site_conf_dir);
    if (dir == NULL) {
        errmsg = strerror(errno);
        // the buf is released when g_http_site_mgr destroyed.
        buf_len = strlen(errmsg) + site_conf_dir_len + 128;
        buf = znx_thread_malloc(buf_len);
        g_http_site_mgr->err.data = buf;
        g_http_site_mgr->err.len = (size_t)(znx_snprintf(buf, buf_len,
            "read %s failed, err: %s", site_conf_dir, errmsg) - buf);
        return ZNX_FALSE;
    }

    struct dirent           *entry = NULL;
    znx_http_site_conf_t    *site_conf = NULL;
    char                    *filename_buf = NULL;
    size_t                  filename_buf_size = 0;
    size_t                  entry_name_len = 0;
    size_t                  off = 0;
    znx_str_t               servername = ZNX_NULL_STRING;
    uint64_t                bucket_index = 0;

    while ((entry = readdir(dir)) != NULL) {
        if (entry->d_type != DT_REG) {
            continue;
        }

        entry_name_len = strlen(entry->d_name);
        
        // find files with the suffix ".yaml"
        if (entry_name_len < 5 ||
            znx_strncmp(entry->d_name + entry_name_len - 5, ".yaml", 5) != 0)
        {
            continue;
        }

        servername.data = (u_char *)entry->d_name;
        servername.len = entry_name_len - 5;

        if (filename_buf_size < site_conf_dir_len + entry_name_len + 128) {
            if (filename_buf) {
                znx_thread_free(filename_buf);
            }
            filename_buf = znx_thread_calloc(site_conf_dir_len + entry_name_len + 128);
            filename_buf_size = site_conf_dir_len + entry_name_len + 128;
        }

        off = 0;
        znx_memcpy(filename_buf+off, site_conf_dir, site_conf_dir_len);
        off += site_conf_dir_len;
        znx_memcpy(filename_buf+off, entry->d_name, entry_name_len);
        off += entry_name_len;
        filename_buf[off] = '\0';

        site_conf = znx_http_site_conf_create();
        if (!znx_http_site_conf_parse(site_conf, filename_buf, &servername)) {
            // the buf is released when g_http_site_mgr destroyed.
            buf_len = site_conf->err.len + off + 128;
            buf = znx_thread_malloc(buf_len);
            g_http_site_mgr->err.data = buf;
            g_http_site_mgr->err.len = (size_t)(znx_snprintf(buf, buf_len,
                "parse %s failed, err: %v", filename_buf, &site_conf->err) - buf);
            if (filename_buf) {
                znx_thread_free(filename_buf);
            }
            closedir(dir);
            return ZNX_FALSE;
        }

        bucket_index = znx_hash_tab_get_bucket_index(g_http_site_mgr->hash_tab,
            site_conf->hash_node.hash_key);
        znx_rwlock_wlock(g_http_site_mgr->rwlocks + bucket_index);
        znx_hash_tab_add(g_http_site_mgr->hash_tab, &site_conf->hash_node);
        znx_rwlock_wunlock(g_http_site_mgr->rwlocks + bucket_index);
    }

    if (filename_buf) {
        znx_thread_free(filename_buf);
    }
    closedir(dir);
    return ZNX_TRUE;
}


static inline znx_http_addr_site_conf_t *
znx_http_addr_site_conf_lookup_helper(znx_hash_node_t *target,
    znx_address_t *listen_address)
{
    znx_http_site_conf_t            *site_conf;
    znx_hash_node_t                 *node;
    znx_http_addr_site_conf_t       *addr_site, *addr_sitep;
    znx_address_t                   *address, *addressp;

    node = znx_hash_tab_get(g_http_site_mgr->hash_tab, target);
    if (node == NULL) {
        return NULL;
    }

    site_conf = znx_queue_data(node, znx_http_site_conf_t, hash_node);
    if (site_conf->addr_sites == NULL) {
        return NULL;
    }

    addr_sitep = site_conf->addr_sites->elts;
    for (size_t i = 0; i < site_conf->addr_sites->nelts; i++) {
        addr_site = addr_sitep + i;
        if (listen_address == NULL || addr_site->address_arr == NULL) {
            znx_http_site_conf_hold(site_conf);
            return addr_site;
        }

        addressp = addr_site->address_arr->elts;
        for (size_t j = 0; j < addr_site->address_arr->nelts; j++) {
            address = addressp + j;
            if (znx_memcmp(address, listen_address, sizeof(znx_address_t)) == 0) {
                znx_http_site_conf_hold(site_conf);
                return addr_site;
            }
        }   
    }

    return NULL;
}


znx_http_addr_site_conf_t *
znx_http_addr_site_lookup(znx_str_t *host,
    znx_address_t *listen_address, znx_bool_t *not_found, char **err)
{
    znx_http_addr_site_conf_t       *addr_site;
    size_t                          len;
    u_char                          buf[320] = { 0 };
    u_char                          *src = host->data;
    u_char                          *src_end = src + host->len;
    uint64_t                        bucket_index = 0;

    if (host->len > 256) {
        *err = "host is too long";
        return NULL;
    }

    *err = NULL;
    *not_found = ZNX_FALSE;

    znx_http_site_conf_t            target;
    target.hash_node.hash_key = znx_crc32_short_lower(host->data, host->len);
    target.server_name = *host;

    bucket_index = znx_hash_tab_get_bucket_index(
        g_http_site_mgr->hash_tab, target.hash_node.hash_key);
    znx_rwlock_rlock(g_http_site_mgr->rwlocks + bucket_index);
    addr_site = znx_http_addr_site_conf_lookup_helper(
        &target.hash_node, listen_address);
    znx_rwlock_runlock(g_http_site_mgr->rwlocks + bucket_index);
    if (addr_site) {
        return addr_site;
    }

    // Avoid staring with '.'
    src++;

    int depth = g_http_main_conf->lookup_depth;
    for (int i = 0; i < depth; i++) {
        while (src < src_end) {
            if (*src == '.') {
                break;
            }
            src++;
        }

        if (src == src_end) {
            break;
        }

        buf[0] = '*';
        len = (size_t)(src_end - src);
        znx_memcpy(buf+1, src, len);
        len += 1;
        buf[len] = '\0';
        target.hash_node.hash_key = znx_crc32_short_lower(buf, len);
        target.server_name.data = buf;
        target.server_name.len = len;
        // skip char '.'
        src++;

        bucket_index = znx_hash_tab_get_bucket_index(
            g_http_site_mgr->hash_tab, target.hash_node.hash_key);
        znx_rwlock_rlock(g_http_site_mgr->rwlocks + bucket_index);
        addr_site = znx_http_addr_site_conf_lookup_helper(
            &target.hash_node, listen_address);
        znx_rwlock_runlock(g_http_site_mgr->rwlocks + bucket_index);
        if (addr_site) {
            return addr_site;
        }
    }

    *not_found = ZNX_TRUE;
    return NULL;
}
